/**
 * 
 */
package it.unitn.testbed.server.serial;

import java.lang.reflect.Method;

import org.apache.log4j.Logger;

/**
 * Runs a method of a class and sets up a timeout to wait for its execution to
 * complete.
 * 
 * First, you should use the constructor
 * {@link #SandboxInvoker(Method, Object, Object[])} to instantiate a new
 * sandbox invoker. In this constructor you pass the object, the method and the
 * argument for the method. Then, to call the method in an sandbox environemnt,
 * you need to call {@link #invoke()}. If the execution does not complete within
 * {@link #TIMEOUT}ms, the sandbox is terminated.
 * 
 * @author Stefan Guna
 * 
 */
public class SandboxInvoker extends Thread {
	public static Logger log = Logger.getLogger(SandboxInvoker.class.getName());
	/** Timeout to wait for completiong. */
	private static final int TIMEOUT = 1000;
	private Object args[];
	private Method method;
	private Object obj;
	private Object result = null;

	/**
	 * Builds a new instance.
	 * 
	 * @param method
	 *            The method to call.
	 * @param obj
	 *            The object which the method belongs to.
	 * @param args
	 *            The arguments for the method.
	 */
	public SandboxInvoker(Method method, Object obj, Object args[]) {
		this.method = method;
		this.obj = obj;
		this.args = args;
	}

	/**
	 * Runs the method in an sandbox environment.
	 * 
	 * @return The object returned by the execution of the method or null if the
	 *         execution was interrupted.
	 */
	synchronized public Object invoke() {
		start();

		try {
			wait(TIMEOUT);
		} catch (InterruptedException e) {
			log.error("Somebody intrerupted execution of " + method + ": "
					+ e.getMessage());
			for (StackTraceElement ste : e.getStackTrace())
				log.error(ste);
			return null;
		}

		if (result == null) {
			interrupt();
			log.warn("Timeout ocurred when invoking " + method);
			return null;
		}

		return result;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Thread#run()
	 */
	@Override
	public void run() {
		try {
			result = method.invoke(obj, args);
		} catch (Exception e) {
			log.warn("Cannot invoke " + method + ": " + e.getMessage());
		}
		synchronized (this) {
			notify();
		}
	}
}
